/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package edu.isi.pegasus.planner.parser;

import edu.isi.pegasus.common.logging.LogManager;
import edu.isi.pegasus.common.util.CondorVersion;
import edu.isi.pegasus.planner.common.PegasusProperties;

import edu.isi.pegasus.common.util.DynamicLoader;

import edu.isi.pegasus.planner.classes.PegasusBag;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;



/**
 * A factory class to load the appropriate Site Catalog Parser implementations
 * based on version in the site catalog element of the XML document
 *
 * @author Karan Vahi
 * @version $Revision$
 */
public class SiteCatalogXMLParserFactory {


    /**
     * Package to prefix "just" class names with.
     */
    public static final String DEFAULT_PARSER_PACKAGE_NAME =
                                             "edu.isi.pegasus.planner.parser";


    
    /*
     * Predefined Constant for Site Catalog Version 2.0
     */
    private static final long SC_VERSION_2_0_0 = CondorVersion.numericValue( "2.0.0" );

    /*
     * Predefined Constant for Site Catalog Version 3.0
     */
    public static final long SC_VERSION_3_0_0 = CondorVersion.numericValue( "3.0.0" );


    /*
     * Predefined Constant for Site Catalog Version 4.0
     */
    public static final long SC_VERSION_4_0_0 = CondorVersion.numericValue( "4.0.0" );


    /**
     * The default Site Catalog Parser
     */
    public static final String DEFAULT_SC_PARSER_CLASS = "SiteCatalogXMLParser4";

    /**
     * The  SC Parser classname
     */
    public static final String SC_PARSER3_CLASS = "SiteCatalogXMLParser3";



    /**
     * Loads the appropriate DAXParser looking at the dax schema that is specified by
     * the user.
     *
     * @param bag                bag of Pegasus intialization objects
     * @param file               the site catalog file
     * @param sites the list of sites that need to be parsed. * means all
     *
     * @return the SiteCatalogXMLParser class that is loaded.
     *
     * @exception SiteCatalogXMLParserFactoryException that nests any error that
     *            might occur during the instantiation
     *
     * @see #DEFAULT_CALLBACK_PACKAGE_NAME
     */
    public static SiteCatalogXMLParser loadSiteCatalogXMLParser( PegasusBag bag,  String file , List<String> sites ) throws SiteCatalogXMLParserFactoryException{



        String scClass = SiteCatalogXMLParserFactory.DEFAULT_SC_PARSER_CLASS;
        LogManager logger = bag.getLogger();
        PegasusProperties properties = bag.getPegasusProperties();


        //sanity check
        if( properties == null){
            throw new RuntimeException("Invalid properties passed");
        }
        if( logger == null){
            throw new RuntimeException("Invalid logger passed");
        }

        //try to figure out the schema version by parsing the dax file
        String schemaVersion = null;

        try{
            Map m = getMetadata( bag, file );
            if( m.containsKey( "version" ) && (schemaVersion = (String)m.get( "version" )) != null  ){
                    logger.log( "Site Catalog Schema Version as determined from catalog file " + schemaVersion,
                            LogManager.DEBUG_MESSAGE_LEVEL );

                    //append .0 to the version number
                    //to be able to convert to numberic value
                    schemaVersion = schemaVersion + ".0";

            }

            //try to figure out the schema from the schemaLocation
            //attribute in the root element
            String schemaLocation = (String) m.get( "schemaLocation");

            if( schemaVersion == null && schemaLocation != null ){
                logger.log( "Guessing schema version from schema location " + schemaLocation,
                            LogManager.DEBUG_MESSAGE_LEVEL );
                //split on whitespace
                String constitutents[] = schemaLocation.split(file);
                schemaLocation = constitutents[ constitutents.length -1 ];

                //get the basename
                String schema = new File( schemaLocation ).getName();
                if( schema.startsWith( "sc-" )  && schema.endsWith( ".xsd" ) ){
                    schemaVersion = schema.substring( schema.indexOf( "sc-" ) + 3,
                                                   schema.lastIndexOf(".xsd") );

                    
                    logger.log( "Site Catalog XML Version as determined from schema location " + schemaVersion,
                        LogManager.DEBUG_MESSAGE_LEVEL );
                    
                    //append .0 to the version number
                    //to be able to convert to numberic value
                    schemaVersion = schemaVersion + ".0";
        
                }            
            }

        }
        catch( Exception e ){
            logger.log( "Problem while determining the version of dax" , e,
                            LogManager.ERROR_MESSAGE_LEVEL );
        }

        if( schemaVersion != null ){
            long numericValue = CondorVersion.numericValue(schemaVersion);

            if( numericValue < SiteCatalogXMLParserFactory.SC_VERSION_2_0_0 ){
                //log error
                throw new SiteCatalogXMLParserFactoryException( "Site Catalog Schema Version2 is no longer supported" );
            }
            else if( numericValue == SiteCatalogXMLParserFactory.SC_VERSION_3_0_0 ){
                scClass = SiteCatalogXMLParserFactory.SC_PARSER3_CLASS;
            }
            else{
                scClass = SiteCatalogXMLParserFactory.DEFAULT_SC_PARSER_CLASS;
            }
        }

        logger.log( "Site Catalog Parser Class to be loaded is " + scClass,
                        LogManager.CONFIG_MESSAGE_LEVEL );
        

        return loadSiteCatalogParser( scClass, bag, sites );
    }
    
    /**
     * Loads the appropriate Site Catalog Parser looking at the dax schema that is specified by
     * the user.
     *
     * @param classname    the classname of the parser class that needs to be loaded
     * @param bag          bag of Pegasus intialization objects
     * @param sites the list of sites that need to be parsed. * means all
     *
     * @return the DAXParser loaded.
     *
     * @exception SiteCatalogXMLParserFactoryException that nests any error that
     *            might occur during the instantiation
     *
     * @see #DEFAULT_CALLBACK_PACKAGE_NAME
     */
    private static final SiteCatalogXMLParser loadSiteCatalogParser( String classname, PegasusBag bag, List<String> sites  ){
        SiteCatalogXMLParser parser = null;
        try{
            //load the DAX Parser class
            //prepend the package name
            String daxClass = ( classname.indexOf('.') == -1)?
                        //pick up from the default package
                        DEFAULT_PARSER_PACKAGE_NAME + "." + classname:
                        //load directly
                        classname;

            DynamicLoader dl  = new DynamicLoader( daxClass );


            Object argList[] = new Object[2];
            Class classList[] = new Class[2];

            classList[0] = bag.getClass();
            classList[1]   = Class.forName("java.util.List");
            argList[0] = bag;
            argList[1] = sites;

            parser = (SiteCatalogXMLParser)dl.instantiate( classList, argList);

        }
        catch(Exception e){
            throw new SiteCatalogXMLParserFactoryException( "Unable to instantiate SiteCatalogXMLParser class ",
                                                    classname, e);
        }
        return parser;
    }




    /**
     * Returns the metadata stored in the root adag element in the DAX
     * 
     * @param bag   the bag of initialization objects
     * @param file   the site catalog file.
     * 
     * @return Map containing the metadata, else an empty map
     */
    public static Map getMetadata( PegasusBag bag, String file ){


        LogManager logger = bag.getLogger();
        if( logger != null ){
            logger.log( "Retrieving Metadata from the file " + file ,
                        LogManager.DEBUG_MESSAGE_LEVEL );
        }
        
        
        SiteCatalogXMLMetadataParser p = new SiteCatalogXMLMetadataParser( bag, "sitecatalog");
        p.startParser( file );

        return p.getMetadata();
    }
    
    

}

/**
 * A lightweight XML Parser class to just retrieve the meta data in first instance
 * of an element in a XML Document. It used to get the metadata in the root element.
 *
 * @author Karan Vahi
 */
class SiteCatalogXMLMetadataParser extends Parser{


    /**
     * The root element name to look for.
     */
    private String mElement;

    /**
     * The Metadata object that has to be returned.
     */
    private Map<String,String> mMetadata;


    /**
     * A boolean indicating that parsing is done.
     */
    protected boolean mParsingDone;

    /**
     * The overloaded constructor
     *
     * @param bag  the bag of intiialization documents
     * @param element  the root element
     */
    public SiteCatalogXMLMetadataParser( PegasusBag bag, String element ){
        super( bag );
        mElement = element;
        mMetadata = new HashMap();
        mParsingDone = false;
    }


    /**
     * Returns the metadata/attributes assoicated with the element that
     * was parsed for.
     *
     * @return
     */
    public Map<String,String> getMetadata(){
        if( mParsingDone ){
            return mMetadata;
        }
        throw new RuntimeException( "Method called before parsing was started" );
    }


    /**
     * Start Element.
     *
     * @param uri
     * @param local
     * @param raw
     * @param attrs
     *
     * @throws SAXException
     */
    public void startElement(String uri, String local, String raw, Attributes attrs) throws SAXException {

        //check
        if ( local == null  || !local.equalsIgnoreCase( mElement) ) {
            return;
        }

        //usually the element we want to search for is the first one
        for ( int i=0; i < attrs.getLength(); ++i ) {
            String name  =  attrs.getLocalName(i) ;
            String value =  attrs.getValue(i) ;
            mMetadata.put( name, value );
        }

        //call to end document
        this.endDocument();

        //we need to throw an exception to stop the parser
        //ugly
        throw new StopParserException();
    }

    /***
     * An empty implementation
     *
     * @param uri
     * @param localName
     * @param qName
     * @throws SAXException
     */
    public void endElement(String uri, String localName, String qName) throws SAXException {

    }

    /**
     * Sets the boolean indicating parsing is done
     */
    public void endDocument() {
        mParsingDone = true;
    }


    /**
     * The function that starts the parser
     *
     * @param file  the file to be parsed
     */
    public void startParser(String file) {
        try{
            this.testForFile( file );
        }
        catch( Exception e){
            throw new RuntimeException( e );
        }


        //while determining the metadata we are just parsing adag element
        //we want the parser validation to be turned off.
        this.setParserFeature("http://xml.org/sax/features/validation", false);
        this.setParserFeature("http://apache.org/xml/features/validation/schema", false);

        try{
            mParser.parse( file );
        }
        catch( StopParserException e ){
            //ignore
        }
        catch (Exception e) {
            //if a locator error then
            String message = (mLocator == null) ?
                           "SiteCatalogXMLMetadataParser While parsing the file " + file:
                           "SiteCatalogXMLMetadataParser While parsing file " + mLocator.getSystemId() +
                           " at line " + mLocator.getLineNumber() +
                           " at column " + mLocator.getColumnNumber();
            mLogger.logEventCompletion();
            throw new RuntimeException(message, e);
        }
        
    }

    /**
     * Not implemented as yet
     *
     * @return
     */
    public String getSchemaLocation() {
        throw new UnsupportedOperationException("Not supported yet.");
    }


    /**
     * Not implemented as yet
     *
     * @return
     */
    public String getSchemaNamespace() {
        throw new UnsupportedOperationException("Not supported yet.");
    }


    /**
     * Private RuntimeException to stop the SAX Parser
     */
    private static class StopParserException extends RuntimeException{

    }

}